#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <memory>
#include <thread>

using namespace std;

template<typename T>
class ThreadsafeQueue {
private:
    mutable mutex mut;
    queue<T> data_queue;
    condition_variable data_cond;

public:
    ThreadsafeQueue() = default;
    ThreadsafeQueue(ThreadsafeQueue const& other) {
        lock_guard<mutex> lk(other.mut);
        data_queue = other.data_queue;
    }

    void push(T new_value) {
        lock_guard<mutex> lk(mut);
        data_queue.push(new_value);
        data_cond.notify_one();
    }

    void wait_and_pop(T& value) {
        unique_lock<mutex> lk(mut);
        data_cond.wait(lk, [this] { return !data_queue.empty(); });
        value = data_queue.front();
        data_queue.pop();
    }

    shared_ptr<T> wait_and_pop() {
        unique_lock<mutex> lk(mut);
        data_cond.wait(lk, [this] { return !data_queue.empty(); });
        shared_ptr<T> res(make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool try_pop(T& value) {
        lock_guard<mutex> lk(mut);
        if (data_queue.empty())
            return false;
        value = data_queue.front();
        data_queue.pop();
        return true;
    }

    shared_ptr<T> try_pop() {
        lock_guard<mutex> lk(mut);
        if (data_queue.empty())
            return shared_ptr<T>();
        shared_ptr<T> res(make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool empty() const {
        lock_guard<mutex> lk(mut);
        return data_queue.empty();
    }
};

void PrepareData(ThreadsafeQueue<int>& queue) {
    while (true) {
        static int i = 1;
        cout << "threadid=" << this_thread::get_id() << " push: " << i << endl;
        queue.push(i++);
        this_thread::sleep_for(chrono::seconds(1));
    }
}

void ProcessData(ThreadsafeQueue<int>& queue) {
    while (true) {
        int i = 0;
        queue.wait_and_pop(i);
        cout << "threadid=" << this_thread::get_id() << " pop: " << i << endl;
        this_thread::sleep_for(chrono::seconds(1));
    }
}

int main() {
    ThreadsafeQueue<int> queue;
    thread t1(PrepareData, ref(queue));
    thread t2(ProcessData, ref(queue));
    t1.join();
    t2.join();
    return 0;
}
